package puzzle.devtools;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.net.URL;
import java.text.MessageFormat;
import java.util.prefs.Preferences;
import javax.swing.AbstractListModel;
import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SpringLayout;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.dom4j.DocumentException;
import org.newdawn.slick.util.Log;
import org.newdawn.slick.util.ResourceLoader;
import puzzle.core.LevelData;
import puzzle.devtools.AbstractEditor;
import puzzle.devtools.LevelPublisher;
import puzzle.game.MapList;
import puzzle.util.MapStream;
import puzzle.util.ResourceManager;
import puzzle.util.SpringUtilities;

public class LevelPublisher extends AbstractEditor {
  
  private static Preferences prefs = null;
  static {
    try {
      prefs = Preferences.userNodeForPackage(LevelPublisher.class);
    }
    catch (Exception e) {
      Log.warn("Can't use Prefs");
    }
  }
  
  public static void main(String[] args) {
    boolean debug = (args.length>=1 && args[0].equals("-d"));
    Log.setVerbose(debug);
    
    //whether we should print to file
    if (debug) {
      try { Log.out = new PrintStream("debug.txt"); }
      catch (FileNotFoundException e) {}
    }
    
    LevelPublisher.loadUI();
     
    try {
      new LevelPublisher(); 
    }
    catch (Exception e) {
      Log.error(e);
    }
  }
  
  //TODO: move Base64 to util
    
  
  private JList list;
  private MapListModel model;
  private MapStream mapStream;
  private JTextField name,path,desc,author;
  private JPanel infoPane;
  private final String errTitle = ResourceManager.getString("publisher.err", "Error");
  private final String root = ResourceManager.getString("user.root", ".");
  private final String maplistXML = ResourceManager.getString("user.maplist", "ext/maplist.xml");
  
  private BPuzFileFilter bpuzFilter = new BPuzFileFilter();
  private XMLFileFilter xmlFilter = new XMLFileFilter();
  
  private MapListModel defaultModel;
  
  public LevelPublisher() {
    super();
    setTitle(getEditorName());
    setResizable(false);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
    getJFileChooser().setAcceptAllFileFilterUsed(true); 
    getJFileChooser().setFileFilter(xmlFilter);
        
    mapStream = new MapStream();
    
    String defpath = ResourceManager.getString("publisher.dir.default", "ext/maplist.xml");
    defpath = MessageFormat.format(defpath, new Object[]{maplistXML});
    try {
      defaultModel = new MapListModel(defpath); 
    }
    catch (Exception e) {
      try {
        System.out.println(defpath);
        File f = new File(ResourceManager.getResource(defpath).getFile());
        f.createNewFile();
      }
      catch (Exception exc) {
        exc.printStackTrace();
      }
      defaultModel = new MapListModel();
    }
    
    model = defaultModel;
    
    final String addStr = ResourceManager.getString("publisher.ui.add", "Add");
    JButton add = new JButton(addStr);
    add.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        updateEdit();
        
        getJFileChooser().removeChoosableFileFilter(xmlFilter);
        getJFileChooser().setFileFilter(bpuzFilter);
        
        File f = getFile(addStr);
        boolean add = false;
        MapList.Entry e = null;
        
        if (f!=null) {
          String path = f.getPath();
          try {
            path = ResourceManager.getRelativePath(f, new File(root));
          } catch (Exception exc) {
          }
          
          LevelData d = null;
          
          try {
            d = mapStream.fromXML(new FileInputStream(f)); 
            add = true;
            e = new MapList.Entry(path, d);
          } catch (Exception exc) {
            String errStr = ResourceManager.getString("publisher.err.file",
                                               "Invalid map file.");
            JOptionPane.showMessageDialog(LevelPublisher.this,  
                      errStr, errTitle, JOptionPane.ERROR_MESSAGE);
            add = false;
          }
        }
        
        if (add && e!=null) {
          model.add(e);
        }
      }
    });
    
    String btst = ResourceManager.getString("publisher.ui.remove", "Remove");
    JButton remove = new JButton(btst);
    remove.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        int i = list.getSelectedIndex();
        if (i!=-1) {
          updateEdit();
          model.remove(i);
          if (i>model.getSize()-1)
            i=model.getSize()-1;
          list.setSelectedIndex(i);
        }
      }
    });
    
    JPanel page = new JPanel();
    page.setLayout(new BoxLayout(page, BoxLayout.PAGE_AXIS));
    
    final float align = Component.TOP_ALIGNMENT;
    
    //create list
    list = new JList(model);
    
    //create the info pane using list
    createInfoPane();
    
    String listTitle = ResourceManager.getString("publisher.ui.title.list", "Maps");
    list.setBorder(BorderFactory.createTitledBorder(listTitle));
    list.setPreferredSize(new Dimension(250, 100));
    list.setSelectedIndex(0);
    list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    list.setLayoutOrientation(JList.VERTICAL);
    list.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        updateInfo();
      }
    });
    
    JScrollPane listScroller = new JScrollPane(list, 
                        JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                        JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
    
    JPanel topPane = new JPanel(new GridLayout(1, 2));
    topPane.add(list);
    topPane.add(infoPane);
    
    JPanel flow = new JPanel();
    flow.add(add);
    flow.add(remove);
    
    page.add(topPane);
    page.add(flow);
    
    
    getContentPane().add(page, BorderLayout.CENTER);
    
    initMenuBar();
    
    updateInfo();
    setJMenuBar(getJMenuBar());
    pack();
    setLocationRelativeTo(null);
    setVisible(true);
    toFront();
  }
  
  private void initMenuBar() {
    String str = ResourceManager.getString("publisher.ui.menu.default", "Use Default");
    final JMenuItem def = newJMenuItem(str, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        model = defaultModel;
        list.setModel(model);
        list.setSelectedIndex(0);
      }
    });
    
    str = ResourceManager.getString("publisher.ui.menu.publish", "Publish");
    final JMenuItem publish = newJMenuItem(str, new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        MapList m = model.getMapList();
        try {
          updateEdit();
          File f = new File(maplistXML);
          try {
            f.createNewFile();
          } 
          catch (Exception e) {
            e.printStackTrace();
          }
          mapStream.saveMapList(m, new FileOutputStream(f)); 
          Log.info("Saved MapList.");
        } 
        catch (Exception e) {
          String errStr = ResourceManager.getString("publisher.err.save",
                                                "Error saving file.");
          JOptionPane.showMessageDialog(LevelPublisher.this,  
                      errStr, errTitle, JOptionPane.ERROR_MESSAGE);
          e.printStackTrace();
        }
      }
    });
    publish.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, 
                InputEvent.CTRL_MASK));
    
    
    str = ResourceManager.getString("publisher.ui.menu.open", "Import");
    final JMenuItem open = newJMenuItem(str, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        try {
          getJFileChooser().removeChoosableFileFilter(bpuzFilter);
          getJFileChooser().setFileFilter(xmlFilter);
            
          File f = getFile("Import");
          if (f!=null) {
            model = new MapListModel(new FileInputStream(f));
            list.setModel(model);
            list.setSelectedIndex(0);
          }
        }
        catch (Exception e2) {
          String errStr = ResourceManager.getString("publisher.err.maplist",
                                        "Invalid maplist file");
          JOptionPane.showMessageDialog(LevelPublisher.this,  
                      errStr, errTitle, JOptionPane.ERROR_MESSAGE);
        }
      }
    });
    open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 
                InputEvent.CTRL_MASK));
    
    str = ResourceManager.getString("publisher.ui.menu.save", "Export");
    final JMenuItem save = newJMenuItem(str, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        getJFileChooser().removeChoosableFileFilter(bpuzFilter);
        getJFileChooser().setFileFilter(xmlFilter);
          
        File f = getFile("Export");
        
        if (f!=null) {
          if (getJFileChooser().getFileFilter() instanceof XMLFileFilter) {
            f = toExtension(f, "xml");
          }
                
          //starts as false if we are checking replace
          //otherwise starts as true and skips exist check
          boolean save = false;
              
          //if we are checking and the file exists
          if (f.exists()) {
            int i = JOptionPane.showConfirmDialog(LevelPublisher.this, 
                                ResourceManager.getString("err.exists",
                                        "File already exists. Replace?"), 
                                errTitle,
                                JOptionPane.YES_NO_OPTION, 
                                JOptionPane.WARNING_MESSAGE);
            save = (i==JOptionPane.YES_OPTION);
          }
          else {
            save = true;
          }
              
          if (save) {
            MapList m = model.getMapList();
            try {
              updateEdit();
              mapStream.saveMapList(m, new FileOutputStream(f)); 
              Log.info("Saved MapList.");
            } 
            catch (Exception e2) {
              String errStr = ResourceManager.getString("publisher.err.save",
                                                    "Error saving file.");
              JOptionPane.showMessageDialog(LevelPublisher.this,  
                          errStr, errTitle, JOptionPane.ERROR_MESSAGE);
              e2.printStackTrace();
            }
          } //end if save
        } //end if non-null
      }
    });
    save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, 
                InputEvent.CTRL_MASK));
    
    
    str = ResourceManager.getString("ui.menu.file", "File");
    final JMenu menu = new JMenu(str);
    menu.add(def);
    menu.add(publish);
    menu.addSeparator();
    menu.add(open);
    menu.add(save);
    menu.addSeparator();
    menu.add(newQuitMenuItem()); 
    
    str = ResourceManager.getString("ui.menu.help", "Help");
    final JMenu help = new JMenu(str);
    help.add(newAboutMenuItem());
    
    
    getJMenuBar().add(menu);
    getJMenuBar().add(help);
  }
  
  public String getPrefix() {
    return "publisher";
  }
  
  protected void createInfoPane() {
    String s0 = ResourceManager.getString("publisher.ui.path", "Path");
    String s1 = ResourceManager.getString("publisher.ui.name", "Map Name");
    String s2 = ResourceManager.getString("publisher.ui.author", "Author");
    String s3 = ResourceManager.getString("publisher.ui.description", "Description");
    JLabel l0 = new JLabel(s0+":", JLabel.TRAILING);
    JLabel l1 = new JLabel(s1+":", JLabel.TRAILING);
    JLabel l2 = new JLabel(s2+":", JLabel.TRAILING);
    JLabel l3 = new JLabel(s3+":", JLabel.TRAILING);
      
    path = new JTextField(8);
    path.setEditable(false);
    
    ActionListener updateEdit = new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        updateEdit();
      }
    };
    
    name = new JTextField(8);
    name.addActionListener(updateEdit);
    
    author = new JTextField(8);
    author.addActionListener(updateEdit);
        
    desc = new JTextField(8);
    desc.addActionListener(updateEdit);
        
    JPanel spring = new JPanel(new SpringLayout());
    spring.add(l0);
    spring.add(path);
    spring.add(l1);
    spring.add(name);
    spring.add(l2);
    spring.add(author);
    spring.add(l3);
    spring.add(desc);
    
    SpringUtilities.makeCompactGrid(spring,
                                    4, 2, //rows, cols
                                    6, 6, //initX, initY
                                    6, 6);//xPad, yPad
    
    JButton refresh = new JButton(ResourceManager.getString("publisher.ui.update", "Update"));
    refresh.addActionListener(updateEdit);
    
    JPanel buttonPane = new JPanel(new FlowLayout(FlowLayout.RIGHT));
    buttonPane.add(refresh);
    
    infoPane = new JPanel(new BorderLayout());
    infoPane.add(spring, BorderLayout.NORTH);
    infoPane.add(buttonPane, BorderLayout.SOUTH);
    
    String s = ResourceManager.getString("publisher.ui.title.info", "Map Info");
    infoPane.setBorder(BorderFactory.createTitledBorder(s));
  }
  
  protected void updateEdit() {
    int i = list.getSelectedIndex();
    if (i==-1||i<0||i>model.getSize()-1) {
      return;
    }
    
    String nname = name.getText();
    String nauthor = author.getText();
    String ndesc = desc.getText();
        
    LevelData d = model.getData(i);
    d.setName(nname);
    d.setAuthor(nauthor);
    d.setDescription(ndesc);
    list.setModel(model);
    list.setSelectedIndex(i);
  }
  
  protected void updateInfo() {
    int i = list.getSelectedIndex();
    if (i==-1||i<0||i>model.getSize()-1) {
      path.setText("");
      name.setText("");
      author.setText("");
      desc.setText("");
      return;
    }
    
    String pathstr = model.getPath(i);
    LevelData d = model.getData(i);
    String namestr = d.getName();
    String authorstr = d.getAuthor();
    String descstr = d.getDescription();
    
    path.setText(pathstr);
    name.setText(namestr);
    author.setText(authorstr);
    desc.setText(descstr);
  }
  
  public Preferences getPrefs() {
    return prefs;
  }
  
  public String getEditorName() {
    return ResourceManager.getString("publisher.title", "BPuz Level Publisher");
  }
  
  private class MapListModel extends AbstractListModel {
    
    MapList list;
    
    public MapListModel() {
      list = new MapList();
    }
    
    public MapListModel(InputStream in) throws IOException, DocumentException, RuntimeException {
      list = mapStream.loadMapList(in);
    }
    
    public MapListModel(String ref) throws IOException, DocumentException, RuntimeException {
      this(ResourceLoader.getResourceAsStream(ref));
    }
    
    public void add(MapList.Entry e) {
      if (list.contains(e)) 
        return;
      int index = list.size();
      list.add(e);
      fireIntervalAdded(this, index, index);
    }
    
    public void add(int i, MapList.Entry e) {
      if (list.contains(e)) 
        return;
      list.add(i, e);
      fireIntervalAdded(this, i, i);
    }
    
    public boolean remove(MapList.Entry e) {
      int index = list.indexOf(e);
      boolean rv = list.remove(e);
      if (index >= 0) {
        fireIntervalRemoved(this, index, index);
      }
      return rv;
    }
    
    public MapList.Entry remove(int index) {
      MapList.Entry rv = list.get(index);
      list.remove(index);
      fireIntervalRemoved(this, index, index);
      return rv;
    }
    
    public MapList.Entry getElementAt(int index) {
      return list.get(index);
    }
    
    public String getPath(int index) {
      return list.getPath(index);
    }
    
    public LevelData getData(int index) {
      return list.getData(index);
    }
    
    public int getSize() {
      return list.size();
    }
    
    public MapList getMapList() {
      return list;
    }
  }
}